package cmd

import (
	"fmt"
	"mi/compiler/diagnostic"
	"mi/compiler/host"
	"mi/compiler/performance"
	"mi/compiler/program"
	"mi/compiler/scanner"
	"mi/internal/xpath"
	"strconv"
)

func reportEmittedFiles(files []xpath.URI) {
	if len(files) == 0 {
		return
	}

	// var currentDir = system.CurrentDirectory()
	for _, file := range files {
		// var filepath = core.GetNormalizedAbsolutePath(file, currentDir)
		fmt.Println("MIFILE: " + file.Filename())
	}
}

func countLines(program program.Program) int {
	var count = 0
	for _, file := range program.GetSourceFiles() {
		count += scanner.GetFileLineAndCharacterFromPosition(file, file.End()).Line
	}
	return count
}

func getDiagnosticText(message *diagnostic.Message, args ...interface{}) string {
	var diagnostic = diagnostic.CreateCompilerDiagnostic(message, args)
	return diagnostic.MessageText
}

func reportDiagnostic(diagnostic *diagnostic.Diagnostic, host program.FormatDiagnosticsHost) {
	fmt.Println(program.FormatDiagnostics(host, diagnostic))
}

func reportDiagnostics(diagnostics []*diagnostic.Diagnostic, host program.FormatDiagnosticsHost) {
	for i := 0; i < len(diagnostics); i++ {
		reportDiagnostic(diagnostics[i], host)
	}
}

func padLeft(s string, length int) string {
	for len(s) < length {
		s = " " + s
	}
	return s
}

func padRight(s string, length int) string {
	for len(s) < length {
		s += " "
	}
	return s
}

func reportStatisticalValue(name string, value string) {
	fmt.Println(padRight(name+":", 12) + padLeft(value, 10))
}

func reportCountStatistic(name string, count int) {
	reportStatisticalValue(name, ""+strconv.Itoa(count))
}

func reportTimeStatistic(name string, time int64) {
	reportStatisticalValue(name, fmt.Sprintf("%.2f", float64(time)/1000)+"s")
}


const Success = 0
const Fail = 1

func compileProgram(pg program.Program, host host.CompilerHost, run bool) int {
	var diags []*diagnostic.Diagnostic

	diags = append(diags, pg.GetOptionsDiagnostics()...)
	diags = append(diags, pg.GetGlobalDiagnostics()...)

	if len(diags) == 0 {
		diags = pg.GetCompileDiagnostics()
	}

	// Otherwise, emit and report any errors we ran into.
	if len(diags) == 0 {
		var emitOutput = pg.Emit()
		diags = append(diags, emitOutput.Diagnostics...)
	}

	if len(diags) == 0 {
		var buildResult = pg.Build(run)
		diags = append(diags, buildResult.Diagnostics...)
	}

	reportDiagnostics(diagnostic.SortAndDeduplicateDiagnostics(diags), host)
	if len(diags) > 0 {
		return Fail
	}

	return Success
}

func compile(options compileOptions, host host.CompilerHost) (program.Program, int) {
	var hasDiagnostics = options.Diagnostics || options.ExtendedDiagnostics
	if hasDiagnostics {
		performance.Enable()
	}

	var pg = program.CreateProgram(options.Files, host)
	var exitStatus = compileProgram(pg, host, options.Run)

	if options.ListFiles {
		for _, file := range pg.GetSourceFiles() {
			fmt.Println(file.Uri)
		}
	}

	if hasDiagnostics {
		reportCountStatistic("Files", len(pg.GetSourceFiles()))
		reportCountStatistic("Lines", countLines(pg))
		reportCountStatistic("Nodes", pg.GetNodeCount())
		reportCountStatistic("Identifiers", pg.GetIdentifierCount())
		reportCountStatistic("Symbols", pg.GetSymbolCount())
		reportCountStatistic("Types", pg.GetTypeCount())

		var programTime = performance.GetDuration("Parse")
		var bindTime = performance.GetDuration("Bind")
		var checkTime = performance.GetDuration("Check")
		var emitTime = performance.GetDuration("Emit")

		if options.ExtendedDiagnostics {
			performance.ForEachMeasure(func(measureName string, duration int64) {
				reportTimeStatistic(measureName + " time", duration)
			})
		} else {
			// Individual component times.
			// Note: To match the behavior of previous versions of the compiler, the reported parse time includes
			// I/O read time and processing time for triple-slash references and module imports, and the reported
			// emit time includes I/O write time. We preserve this behavior so we can accurately compare times.
			reportTimeStatistic("I/O read", performance.GetDuration("I/O Read"))
			reportTimeStatistic("I/O write", performance.GetDuration("I/O Write"))
			reportTimeStatistic("Parse time", programTime)
			reportTimeStatistic("Bind time", bindTime)
			reportTimeStatistic("Check time", checkTime)
			reportTimeStatistic("Emit time", emitTime)
		}
		reportTimeStatistic("Total time", programTime + bindTime + checkTime + emitTime)

		performance.Disable()
	}

	return pg, exitStatus
}